class Type {
    // type
    static number = new Type('number')
    static boolean = new Type('boolean')
    static string = new Type('string')
    static null = new Type('null')
    static variable = new Type('variable')

    // keyword: break, const, continue, if, else, while, for
    // function, return, this, var
    static keyword = new Type('keyword')

    // operator
    static auto = new Type('auto')
    static colon = new Type('colon')
    static comma = new Type('comma')
    static semicolon = new Type('semicolon')
    static equal = new Type('equal')
    static assign = new Type('assign')

    static notEqual = new Type('notEqual')
    static greaterThan = new Type('greaterThan')
    static lessThan = new Type('lessThan')
    static greaterEqual = new Type('greaterEqual')
    static lessEqual = new Type('lessEqual')

    static plus = new Type('plus')
    static minus = new Type('minus')
    static multiply = new Type('multiply')
    static divide = new Type('divide')

    static and = new Type('and')
    static or = new Type('or')
    static not = new Type('not')

    static mod = new Type('mod')
    static dot = new Type('dot')
    // [
    static bracketLeft = new Type('bracketLeft')
    // ]
    static bracketRight = new Type('bracketRight')
    // {
    static curlyLeft = new Type('curlyLeft')
    // }
    static curlyRight = new Type('curlyRight')
    // (
    static parenthesesLeft = new Type('parenthesesLeft')
    // )
    static parenthesesRight = new Type('parenthesesRight')
    //TODO
    static functionName = new Type('functionName')


    constructor(name) {
        this.enumName = name
    }

    toString() {
        return `${this.constructor.name}.${this.enumName}`
    }
}

const getEmptyString = (len) => {
    let r = ''
    for (let i = 0; i < len; i++) {
        r += ' '
    }
    return r
}

class Token {
    constructor(type, value, startRow = 0, startCol = 0, endRow = 0, endCol = 0) {
        this.tokenValue = value
        this.tokenType = type
        this.startRow = startRow
        this.startCol = startCol
        this.endRow = endRow
        this.endCol = endCol
    }

    printToken = () => {
        let format = ''
        const value = `值:(${this.tokenValue})`
        const type = `类型:${[this.tokenType]}`
        const row = `起始行:${this.startRow}`
        const col = `起始列:${this.startCol}`
        const endRow = `结束行:${this.endRow}`
        const endCol = `结束列:${this.endCol}`

        const valueLen = 15
        const typeLen = 30
        const rowLen = 10
        const colLen = 10
        const endRowLen = 10
        const endColLen = 10

        format += value + getEmptyString(valueLen - value.length) +
            type + getEmptyString(typeLen - type.length) +
            row + getEmptyString(rowLen - row.length) +
            col + getEmptyString(colLen - col.length) +
            endRow + getEmptyString(endRowLen - row.length) +
            endCol + getEmptyString(endColLen - col.length)

        log(format)
    }
}

class TokenList {
    constructor(glString) {
        this.tokenList = this.tokenizer(glString)
        this._i = 0
    }

    nextToken = () => {
        if (this._i === this.tokenList.length) {
            return null
        }
        var t = this.tokenList[this._i]
        this._i += 1
        return t
    }

    // 基本类型：数字、字符串、true、false、null
    numberEnd = (s, begin) => {
        // 如果是 '-123'是数字，如果是：'-a'、'- 0123'、'a-1314'
        // 应该被解析成减号
        // 减号就直接返回，要放外面处理
        // TODO: 先补解决a-1314的case

        const numbers = '0123456789'
        const hexNumbers = 'ABCDEF'
        let output = ''
        let xcuuTag = false
        let fuhkTag = false
        let i = begin

        if (s[i] === '-' && !numbers.includes(s[i + 1])) {
            // 非数字
            return ['-', 1, Type.minus]
        }

        // 10进制和16进制
        if (s[i] === '0') {
            if (s.slice(i, i + 2) === '0b') {
                output += '0b'
                i += 2
            } else if (s.slice(i, i + 2) === '0x') {
                output += '0x'
                i += 2
            }
        }

        // 只能遇到一次负号和小数点
        while (i < s.length) {
            const c = s[i]
            if (c === '.' && xcuuTag === false) {
                // 第一次遇到小数
                output += c
                xcuuTag = true
            } else if (c === '-' && fuhkTag === false) {
                output += c
                fuhkTag = true
            } else if (numbers.includes(c) || hexNumbers.includes(c)) {
                output += c
            } else {
                break
            }
            i += 1
        }
        // log(output, i - begin)
        return [Number(output), i - begin, Type.number]
    }

    stringEnd = (s, begin) => {
        let output = ''
        let i = begin
        const escape = {
            't': '\t',
            '0': '\0',
            'b': '\b',
            'n': '\n',
            'v': '\v',
            'f': '\f',
            'r': '\r',
            '"': '\"',
            "'": '\'',
            '\\': '\\',
            '/': '\/'
        }
        // log(`s=(${s})`)
        while (i < s.length) {
            const c = s[i]
            // log(`(${c})`)
            if (c === '"' || c === "'") {
                break
            } else if (c === '\\') {
                const next = s[i + 1]
                if (escape[next]) {
                    output += escape[next]
                } else {
                    log('不存在该转义字符')
                }
                i += 1
            } else {
                output += c
            }
            i += 1
        }
        return [output, i - begin, Type.string]
    }

    keywordOrLogicEnd = (s, begin) => {
        let i = begin
        const c = s[i]
        // 关键词
        if (c === 't' && s.slice(i, i + 4) === 'true') {
            return [true, 4, Type.boolean]
        } else if (c === 'f' && s.slice(i, i + 5) === 'false') {
            return [false, 5, Type.boolean]
        } else if (c === 'n' && s.slice(i, i + 4) === 'null') {
            // null
            return [null, 4, Type.null]
        } else if (c === 'v' && s.slice(i, i + 3) === 'var') {
            // var
            return ['var', 3, Type.keyword]
        } else if (c === 'c' && s.slice(i, i + 5) === 'const') {
            // const
            return ['const', 5, Type.keyword]
        } else if (c === 'c' && s.slice(i, i + 8) === 'continue') {
            // continue
            return ['continue', 8, Type.keyword]
        } else if (c === 'b' && s.slice(i, i + 5) === 'break') {
            // break
            return ['break', 5, Type.keyword]
        } else if (c === 'c' && s.slice(i, i + 5) === 'class') {
            // class
            return ['class', 5, Type.keyword]
        } else if (c === 'r' && s.slice(i, i + 6) === 'return') {
            // return
            return ['return', 6, Type.keyword]
        } else if (c === 'i' && s.slice(i, i + 2) === 'if') {
            // if
            return ['if', 2, Type.keyword]
        } else if (c === 'e' && s.slice(i, i + 4) === 'else') {
            // else
            return ['else', 4, Type.keyword]
        } else if (c === 'w' && s.slice(i, i + 5) === 'while') {
            // while
            return ['while', 5, Type.keyword]
        } else if (c === 'f' && s.slice(i, i + 3) === 'for') {
            // for
            return ['for', 3, Type.keyword]
        } else if (c === 'f' && s.slice(i, i + 8) === 'function') {
            // function
            return ['function', 8, Type.keyword]
        } else if (c === '=' && s[i + 1] !== '=') {
            // 逻辑运算符
            return ['=', 1, Type.assign]
        } else if (c === '=' && s.slice(i, i + 2) === '==') {
            return ['==', 2, Type.equal]
        } else if (c === '!' && s.slice(i, i + 2) === '!=') {
            return ['!=', 2, Type.notEqual]
        } else if (c === '>' && s[i + 1] !== '=') {
            return ['>', 1, Type.greaterThan]
        } else if (c === '>' && s.slice(i, i + 2) === '>=') {
            return ['>=', 2, Type.greaterEqual]
        } else if (c === '<' && s[i + 1] !== '=') {
            return ['<', 1, Type.lessThan]
        } else if (c === '<' && s.slice(i, i + 2) === '<=') {
            return ['<=', 2, Type.lessEqual]
        } else if (c === 'a' && s.slice(i, i + 3) === 'and') {
            return ['and', 3, Type.and]
        } else if (c === 'o' && s.slice(i, i + 2) === 'or') {
            return ['or', 2, Type.or]
        } else if (c === 'n' && s.slice(i, i + 3) === 'not') {
            return ['not', 3, Type.not]
        }

        // log('既不是关键词 也不是 逻辑运算，是变量')
        return this.variableEnd(s, begin)
    }

    calculatorEnd = (s, begin) => {
        let i = begin
        const c = s[i]

        if (c === '+') {
            return ['+', 1, Type.plus]
        } else if (c === '-') {
            return ['-', 1, Type.minus]
        } else if (c === '*') {
            return ['*', 1, Type.multiply]
        } else if (c === '/') {
            return ['/', 1, Type.divide]
        } else if (c === '%') {
            return ['%', 1, Type.mod]
        }
    }

    variableEnd = (s, begin) => {
        const variable = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789'
        let i = begin
        let output = ''
        while (i < s.length) {
            const c = s[i]
            if (variable.includes(c)) {
                output += c
            } else {
                return [output, i - begin, Type.variable]
            }
            i += 1
        }
        return [output, i - begin, Type.variable]
    }

    isSpace = (e) => {
        return e === ' ' || e === '\t' || e === '\n'
    }

    // 词法解析
    tokenizer = (glString) => {
        const codes = glString + '\n' // 加一个回车来处理更方便
        // -号也要特殊处理
        const numbers = '-0123456789'
        const logicSymbols = '!=><ano' // and not or
        const keyword = 'tcviewfrnb' // true, false, null, const var if else while for function return class
        const calculator = '+-*/%'
        const brackets = '[]{}()'
        // 变量名的开头
        const variable = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
        let output = []

        let i = -1
        let row = 1
        let col = 1
        while (i < codes.length) {
            i += 1
            const c = codes[i]
            if (!this.isSpace(c)) {
                // log('c:', c)
            }
            // 回车单独处理
            if (c === '\n') {
                row += 1
                // 换行的时候 col清零
                // log(row, col)
                col = 1
            } else if (numbers.includes(c)) {
                const n = this.numberEnd(codes, i)
                const value = n[0]
                const type = n[2]
                const token = new Token(type, value, row, col, row, col + toString(n).length - 1)

                // 按移动的距离来算列数
                col += toString(value).length
                output.push(token)
                i += n[1] - 1 // 因为后面可能还有值，要消化掉
            } else if (c === '"' || c === "'") {
                // 拿到string之后，再在string里找回车个数来求end
                const n = this.stringEnd(codes, i + 1)
                const value = n[0]
                // log(`(${value})`)
                const split = value.split('\n')
                let endRow = 0
                let endCol = 0
                if (split.length === 1) {
                    // 字符串string内无换行
                    endRow = row
                    endCol = col + value.length
                } else {
                    // 字符串string内有换行
                    endRow = row + split.length - 1 // 有一个偏移
                    endCol = split[split.length - 1].length + 1 // 增加1个偏移，偏移到银行上
                }
                const type = n[2]
                const token = new Token(type, value, row, col, endRow, endCol)
                col += value.length
                output.push(token)
                i += n[1] + 1
            } else if (logicSymbols.includes(c) || keyword.includes(c)) {
                // 逻辑运算: !=, ==, =, <, >, <=, >=, and, or, not
                // 或者
                // 关键词: var const if while for function class return, true / false, null 可能和not重复
                //        continue break
                const n = this.keywordOrLogicEnd(codes, i)
                const value = n[0]

                const type = n[2]
                const token = new Token(type, value, row, col, row, col + value.length - 1)
                col += value.length
                output.push(token)
                i += n[1] - 1
            } else if (calculator.includes(c)) {
                // + - * / %
                const n = this.calculatorEnd(codes, i)
                const value = n[0]
                const type = n[2]
                const token = new Token(type, value, row, col, row, col + value.length - 1)
                col += value.length
                output.push(token)
                i += n[1]
            } else if (variable.includes(c)) {
                // log('可能是变量:', c)
                // 找到下一个非变量名的
                const n = this.variableEnd(codes, i)
                // log('变量:', n)
                const value = n[0]
                const type = n[2]
                const token = new Token(type, value, row, col, row, col + value.length - 1)
                col += value.length
                output.push(token)
                i += n[1] - 1
            } else if (brackets.includes(c)) {
                const type = {
                    '(': Type.parenthesesLeft,
                    ')': Type.parenthesesRight,
                    '{': Type.curlyLeft,
                    '}': Type.curlyRight,
                    '[': Type.bracketLeft,
                    ']': Type.bracketRight,
                }
                // log('括号:', c)
                const token = new Token(type[c], c, row, col, row, col + c.length - 1)
                col += 1
                output.push(token)
            } else if (',:.'.includes(c)) {
                // log('符号')
                const type = {
                    ',': Type.comma,
                    ':': Type.colon,
                    '.': Type.dot,
                }
                const token = new Token(type[c], c, row, col, row, col + c.length - 1)
                col += 1
                output.push(token)
            } else {
                // 空格才+1
                col += 1
            }
        }
        // this.tokenList = output
        return output
    }

    getTokenList = () => {
        return this.tokenList
    }

    printTokenList = () => {
        for (let i = 0; i < this.tokenList.length; i++) {
            const t = this.tokenList[i]
            t.printToken()
        }
    }
}

const logTest = () => {
    let s12 = `const Animal = class() {
    const new = function(sex, age) {
        const this.age = age
        const a = "test
test"
        if (2 > 1) {
            log('hello')
        }
        this.age = 20
    }
}`

    log('源码: \n', s12)
    const t1 = new TokenList(s12)
    log('tokens:')
    t1.printTokenList()
}
